﻿using System;
using Aspects.PInvoke;
using System.Threading;

namespace Aspects.Custom
{
    public class ECPort
    {
        private const byte LegacyAddress = 0x6C;
        private const byte BufferAddress = 0x68;

        /// <summary>
        /// Reads the Embedded Controller at a given Command from port 0x6C
        /// </summary>
        /// <param name="Command">The Command to be read</param>
        /// <returns>Returns the value from Port 0x68 which has the value from the Command. If it fails it returns 0xFF </returns>
        public static byte ReadEC(ushort Command)
        {
            //the ec can only handle 8bit without the help of mailbox
            if (Command > 0xFF)
            {
                byte uDATA;
                for (int i = 0; i < 30; i++)
                {
                    if (ProcessCommandReadMailbox(Command, out uDATA))
                        return uDATA;
                }
            }
            else
            {
                return ProcessCommandRead(Command);
            }
            return 0xFF;
        }

        /// <summary>
        /// Reads the embedded controller and does not return until it double checks itself
        /// </summary>
        /// <param name="ReadByte">The command to read</param>
        /// <returns>Returns the value that has been double checked</returns>
        public static byte DoubleReadEC(byte ReadByte)
        {
            byte FirstRead = 0x00;
            byte SecondRead = 0x01;

            for (int i = 0; i < 30; i++)
            {
                FirstRead = ReadEC(ReadByte);
                Thread.Sleep(100);
                SecondRead = ReadEC(ReadByte);
                if (FirstRead == SecondRead)
                    break;
            }
            return FirstRead;
        }

        /// <summary>
        /// Writes a byte of Data to the embedded controller
        /// </summary>
        /// <param name="Command">Command Command you want Data wrote to</param>
        /// <param name="Data">Data to write</param>
        /// <returns>Returns true if writting was successful.</returns>
        public static bool WriteEC(ushort Command, byte Data)
        {//not sure if writting using the mailbox is used for our purposes, so it's not implemented here
            if (Command > 0xFF)
            {
                return false;
            }
            else
            {
                ProcessCommandWrite(Command, Data);
                return true;
            }
        }

        /// <summary>
        /// Reads a 16bit Command address
        /// </summary>
        /// <param name="Command">Address to be checked</param>
        /// <param name="Data">[Out] Data that was checked</param>
        /// <returns>True if the Data was successfully verified.</returns>
        private static bool ProcessCommandReadMailbox(ushort Command, out byte Data)
        {
            byte uDATA;
            byte ECAddressLow = (byte)(Command & 0xFF);
            byte ECAddressHigh = (byte)(Command / 0xFF);

            if (!ProcessCommandWrite(0xA3, ECAddressHigh) | !ProcessCommandWrite(0xA2, ECAddressLow))
            {
                Data = 0xFF;
                return false;
            }
            uDATA = ProcessCommandRead(0x00A4);
            if (uDATA > 0x00FF)
            {
                Data = 0xFF;
                return false;
            }
            Data = (byte)(uDATA & 0xFF);
            return true;
        }

        /// <summary>
        /// Processes the request to read from the Embedded Controller
        /// </summary>
        /// <param name="Command">Command address of the embedded Controller</param>
        /// <returns>Returns the data retrieved from the Command address</returns>
        private static byte ProcessCommandRead(ushort Command)
        {
            //check to see if either the input buffer or output buffer is full.
            if (!CheckStatusRegister(StatusRegister.OutputBuffer1 | StatusRegister.InputBuffer1, 0))
                return 0xFF;
            //if they are not full, tell the EC something
            Native.Out32(LegacyAddress, Command);
            for (int i = 0; i <= 3500; i++)
            {
                var OutBuffer = Native.Inp32(LegacyAddress);
                //wait for the output buffer to finish writting to the output port
                if ((OutBuffer & 0x01) > 0)
                {
                    var temp = Native.Inp32(BufferAddress);
                    return (byte)(temp & 0xFF);
                }
            }
            //if it didn't ever stop means it was waiting for host to get Data
            //sort of like an error
            Native.Inp32(BufferAddress);
            return 0xFF;
        }

        /// <summary>
        /// Processes the Command to write a byte of data to a command address
        /// </summary>
        /// <param name="Command">Command address to write data to</param>
        /// <param name="Data">a byte of a data to write</param>
        /// <returns>Returns true if writting was successful</returns>
        private static bool ProcessCommandWrite(ushort Command, byte Data)
        {
            //check to see if either the input buffer or output buffer is full.
            if (CheckStatusRegister(StatusRegister.OutputBuffer1 | StatusRegister.InputBuffer1, 0))
            {//if EC is ready to be Read and Write, send the Command
                Native.Out32(LegacyAddress, Command);
                for (int i = 0; i <= 4000; i++)
                {//wait for InputBuffer to be cleared
                    if (CheckStatusRegister(StatusRegister.InputBuffer1, 0))
                    {//Now that Intput Buffer is cleared, send Data to be wrote
                        Native.Out32(BufferAddress, Data);
                        for (int j = 0; j <= 4000; j++)
                        {//Once the EC reports it is done writting return true.
                            if (CheckStatusRegister(StatusRegister.InputBuffer1, 0))
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            //if the buffers were not clear, then EC is not ready to be wrote to
            Native.Inp32(BufferAddress);
            return false;
        }

        /// <summary>
        /// Checks the Legacy address' status register for certain flags
        /// </summary>
        /// <param name="Flags">Flags to check</param>
        /// <param name="Status">Status you want to check against</param>
        /// <returns></returns>
        private static bool CheckStatusRegister(StatusRegister Flags, int Status)
        {
            ushort Buffers = Native.Inp32(LegacyAddress);
            if ((Buffers & (byte)Flags) == Status)
            {//Buffer is clear and ready for command
                return true;
            }
            else
            {//Clear the Buffer
                Native.Inp32(BufferAddress);
                return false;
            }
        }

        [Flags]
        private enum StatusRegister
        {
            OutputBuffer1 = 0x01,
            InputBuffer1 = 0x02,
            GeneralPurposeFlag = 0x04,
            //A2 is set Data is command, not set Data is Data
            A2 = 0x08,
            //mailbox setup 00b both sides, 01b EC side, 10b host side, 11b reserved
            NoHost = 0x10,
            NoEC = 0x20,
            OutputBuffer2 = 0x40,
            InputBuffer2 = 0x80
        }

    }
}
